# Copyright 2004 Daniel Henninger <jadestorm@nc.rr.com>
# Licensed for distribution under the GPL version 2, check COPYING for details

from twisted.internet import protocol, reactor, defer
from tlib import oscar
from tlib.domish import Element
from twisted.python import log
import config
import utils
import debug
import sys, warnings, pprint



#############################################################################
# BOSConnection
#############################################################################
class B(oscar.BOSConnection):
	def __init__(self,username,cookie,aimcon):
		self.aimcon = aimcon
		self.aimcon.bos = self
		self.session = aimcon.session  # convenience
		self.capabilities = [oscar.CAP_CHAT]
		if (config.crossChat):
			self.capabilities.append(oscar.CAP_CROSS_CHAT)
		oscar.BOSConnection.__init__(self,username,cookie)

	def initDone(self):
		self.requestSelfInfo().addCallback(self.gotSelfInfo)
		self.requestSSI().addCallback(self.gotBuddyList)
		debug.log("B: initDone %s for %s" % (self.username,self.session.jabberID))

	def updateBuddy(self, user):
		from glue import aim2jid

		debug.log("B: updateBuddy %s" % (user))
		buddyjid = aim2jid(user.name)
		ptype = None
		show = None
		status = None
		if (user.flags.count("away")):
			self.getAway(user.name).addCallback(self.sendAwayPresence, user)
		else:
			self.session.sendPresence(to=self.session.jabberID, fro=buddyjid, show=show, status=status, ptype=ptype)
			self.aimcon.contacts.updateSSIContact(user.name, presence=ptype, show=show, status=status)

	def offlineBuddy(self, user):
		from glue import aim2jid

		debug.log("B: offlineBuddy %s" % (user.name))
		buddyjid = aim2jid(user.name)
		show = None
		status = None
		ptype = "unavailable"
		self.session.sendPresence(to=self.session.jabberID, fro=buddyjid, show=show, status=status, ptype=ptype)

	def receiveMessage(self, user, multiparts, flags):
		from glue import aim2jid

		debug.log("B: receiveMessage %s %s %s %s %s" % (self.session.jabberID, self.name, user.name, multiparts, flags))
		sourcejid = aim2jid(user.name)
		text = oscar.dehtml(multiparts[0][0])
		text = text.decode("utf-8", "replace")
		text = text.strip()
		self.session.sendMessage(to=self.session.jabberID, fro=sourcejid, body=text, mtype="chat")
		self.session.pytrans.stats['incmessages'] += 1

	def receiveWarning(self, newLevel, user):
		debug.log("B: receiveWarning [%s] from %s" % (newLevel,hasattr(user,'name') and user.name or None))

	def receiveTypingNotify(self, type, user):
		from glue import aim2jid

		debug.log("B: receiveTypingNotify %s from %s" % (type,hasattr(user,'name') and user.name or None))
		sourcejid = aim2jid(user.name)
		if (type == "begin"):
			self.session.sendTypingNotification(to=self.session.jabberID, fro=sourcejid, typing=True)
		elif (type == "idle"):
			self.session.sendTypingNotification(to=self.session.jabberID, fro=sourcejid, typing=False)
		elif (type == "finish"):
			self.session.sendTypingNotification(to=self.session.jabberID, fro=sourcejid, typing=False)

	def receiveChatInvite(self, user, message, exchange, fullName, instance, shortName, inviteTime):
		debug.log("B: receiveChatInvite from % for room % with message: %s" % (user.name,shortName,message))

	def chatReceiveMessage(self, chat, user, message):
		debug.log("B: chatReceiveMessage to %s from %s:%s" % (chat.name,user.name,message))

	def chatMemberJoined(self, chat, member):
		debug.log("B: chatMemberJoined %s joined %s" % (member.name,chat.name))

	def chatMemberLeft(self, chat, member):
		debug.log("B: chatMemberLeft %s left %s (members: %s)" % (member.name,chat.name,map(lambda x:x.name,chat.members)))

	def receiveSendFileRequest(self, user, file, description, cookie):
		debug.log("B: receiveSendFileRequest")


	# Callbacks
	def sendAwayPresence(self, msg, user):
		from glue import aim2jid
        
		buddyjid = aim2jid(user.name)
		ptype = None
		show = "away"
		status = msg
		self.session.sendPresence(to=self.session.jabberID, fro=buddyjid, show=show, status=status, ptype=ptype)
		self.aimcon.contacts.updateSSIContact(user.name, presence=ptype, show=show, status=status)

	def gotSelfInfo(self, user):
		debug.log("B: gotSelfInfo: %s" % (user.__dict__))
		self.name = user.name

	def gotBuddyList(self, l):
		debug.log("B: gotBuddyList: %s" % (str(l)))
		self.ssigroups = list()
		if (l is not None):
			for g in l[0]:
				debug.log("B: gotBuddyList found group %s" % (g.name))
				self.ssigroups.append(g)
				for u in g.users:
					debug.log("B: got user %s from group %s" % (u.name, g.name))
					self.aimcon.contacts.updateSSIContact(u.name, skipsave=True)
		self.aimcon.contacts.saveXDBBuddies()
		self.activateSSI()
		self.setProfile(None)
		self.setIdleTime(0)
		self.clientReady()
		self.session.ready = True
		tmpjid=config.jid
		if(self.session.registeredmunge):
			tmpjid=config.jid+"/registered"
		self.session.sendPresence(to=self.session.jabberID, fro=tmpjid, show=self.aimcon.savedShow, status=self.aimcon.savedFriendly)
		if (self.aimcon.savedShow in ["online", None]):
			self.aimcon.setAway(None)
		else:
			self.aimcon.setAway(self.aimcon.savedFriendly)

	def warnedUser(self, oldLevel, newLevel, username):
		debug.log("B: warnedUser");

	def createdRoom(self, (exchange, fullName, instance)):
		debug.log("B: createdRoom: %s, %s, %s" % (exchange, fullName, instance))

	def chatJoined(self, chat):
		debug.log("B: chatJoined room %s (members: %s)" % (chat.name,map(lambda x:x.name,chat.members)))



#############################################################################
# Oscar Authenticator
#############################################################################
class OA(oscar.OscarAuthenticator):
	def __init__(self,username,password,aimcon,deferred=None,icq=0):
		self.aimcon = aimcon
		self.BOSClass = B
		oscar.OscarAuthenticator.__init__(self,username,password,deferred,icq)

	def connectToBOS(self, server, port):
		c = protocol.ClientCreator(reactor, self.BOSClass, self.username, self.cookie, self.aimcon)
		return c.connectTCP(server, int(port))



#############################################################################
# AIMConnection
#############################################################################
class AIMConnection:
	def __init__(self, username, password):
		self.username = username
		self.password = password
		self.reactor = reactor
		self.contacts = AIMContacts(self.session)
		self.deferred = defer.Deferred()
		self.deferred.addErrback(self.errorCallback)
		hostport = ("login.oscar.aol.com", 5109)
		debug.log("AIMConnection: client creation for %s" % (self.session.jabberID))
		self.oa = OA
		self.creator = protocol.ClientCreator(self.reactor, self.oa, self.username, self.password, self, deferred=self.deferred, icq=0)
		debug.log("AIMConnection: connect tcp")
		self.creator.connectTCP(*hostport)

		debug.log("AIMConnection: \"%s\" created" % (self.username))
	
	def setAway(self, awayMessage=None):
		debug.log("AIMConnection: setAway %s" % (awayMessage))
		if (not self.session.ready or not hasattr(self, "bos")):
			return

		self.bos.setAway(awayMessage)

	def sendMessage(self, target, message):
		from glue import jid2aim

		self.session.pytrans.stats['outmessages'] += 1
		scrnname = jid2aim(target)
		debug.log("AIMConnection: sendMessage %s %s" % (scrnname, message))
		htmlized = oscar.html(message.encode("iso-8859-1", "replace"))
		if (hasattr(self, "bos")):
			self.bos.sendMessage(scrnname, htmlized)
		else:
			debug.log("AIMConnection: not logged in yet")
			return

	def resendBuddies(self, resource):
		from glue import aim2jid
		debug.log("AIMConnection: resendBuddies %s" % (resource))
		if (not hasattr(self, "contacts")):
			return

		for c in self.contacts.ssicontacts.keys( ):
			debug.log("AIMConnection: resending buddy of %s" % (c))
			jid = aim2jid(c)
			show = self.contacts.ssicontacts[c]['show']
			status = self.contacts.ssicontacts[c]['status']
			ptype = self.contacts.ssicontacts[c]['presence']
			self.session.sendPresence(to=self.session.jabberID, fro=jid, show=show, status=status, ptype=ptype)

	def sendTypingNotify(self, type, dest):
		from tlib.oscar import MTN_FINISH, MTN_IDLE, MTN_BEGIN
		from glue import jid2aim

		username = jid2aim(dest)
		debug.log("AIMConnection: sendTypingNotify %s to %s" % (type,username))
		if (type == "begin"):
			self.bos.sendTypingNotification(username, MTN_BEGIN)
		elif (type == "idle"):
			self.bos.sendTypingNotification(username, MTN_IDLE)
		elif (type == "finish"):
			self.bos.sendTypingNotification(username, MTN_FINISH)

	def getvCard(self, vcard, user):
		debug.log("AIMConnection: getvCard %s" % (user))
		d = defer.Deferred()
		self.bos.getProfile(user).addCallback(self.gotvCard, user, vcard, d)
		return d

        def gotvCard(self, profile, user, vcard, d):
		debug.log("AIMConnection: gotvCard: %s" % (profile))

		profilestart = profile.find("<HTML>")
		cutprofile = oscar.dehtml(profile[profilestart:])
		nickname = vcard.addElement("NICKNAME")
		nickname.addContent(user)
		jabberid = vcard.addElement("JABBERID")
		jabberid.addContent(user+"@"+config.jid)
		desc = vcard.addElement("DESC")
		desc.addContent(cutprofile)

		d.callback(vcard)

	def removeMe(self):
		from glue import aim2jid
		debug.log("AIMConnection: removeMe")
		if (not hasattr(self, "bos")):
			return

		self.bos.stopKeepAlive()
		self.bos.disconnect()

		if (not hasattr(self, "contacts")):
			return

		for c in self.contacts.ssicontacts.keys( ):
			debug.log("AIMConnection: sending offline for %s" % (c))
			jid = aim2jid(c)
			show = self.contacts.ssicontacts[c]['show']
			status = self.contacts.ssicontacts[c]['status']
			ptype = self.contacts.ssicontacts[c]['presence']
			self.session.sendPresence(to=self.session.jabberID, fro=jid, show=show, status=status, ptype=ptype)

	def jabberSubscriptionReceived(self, to, subtype):
		debug.log("AIMConnection: Session \"%s\" - jabberSubscriptionReceived(\"%s\", \"%s\")" % (self.session.jabberID, to, subtype))

		def updatePresence(ptype): # Convenience
			self.session.sendPresence(to=self.session.jabberID, fro=to, ptype=ptype)

		if(to.find('@') > 0): # For contacts
			from glue import jid2aim

			userHandle = jid2aim(to)

			if(subtype == "subscribe"):
				# User wants to subscribe to contact's presence
				debug.log("AIMConnection: Subscribe request received.")
				def cb(arg=None):
					updatePresence("subscribed")

				if (not hasattr(self, "bos")):
					debug.log("Not properly logged in yet")
					return

				savethisgroup = None
				highid = 0
				for g in self.bos.ssigroups:
					for u in g.users:
						if (u.name == userHandle):
							debug.log("User is already in SSI buddy list, skipping.")
							updatePresence("subscribed")
							return
						id = g.findIDFor(u)
						if (id > highid):
							highid = id

					if (g.name == "Buddies"):
						debug.log("Located group %s" % (g.name))
						savethisgroup = g

				newGroup = None
				if (savethisgroup is None):
					#debug.log("Adding required new group")
					
					#newGroup = oscar.SSIGroup("Buddies")

					#self.bos.startModifySSI()
					#self.bos.addItemSSI(newGroup, groupID = 0, buddyID = 0)
					#self.bos.endModifySSI()
					#savethisgroup = newGroup
					debug.log("No Buddies group, need to add one.  Not implemented.")
					return

				newUser = oscar.SSIBuddy(userHandle)
				newUserID = highid+1
				savethisgroup.addUser(newUserID, newUser)

				debug.log("Adding item to SSI")
				self.bos.startModifySSI()
				#if (newGroup is not None):
				#	self.bos.addItemSSI(newGroup, groupID = 50, buddyID = 0)
				self.bos.addItemSSI(newUser).addCallback(cb)
				self.bos.modifyItemSSI(savethisgroup)
				self.bos.endModifySSI()

				self.contacts.updateSSIContact(userHandle)

			elif(subtype == "subscribed"):
				# The user has granted this contact subscription
				debug.log("AIMConnection: Subscribed request received.")
				pass
				# Lets subscribe back, this causes loops...
				#updatePresence("subscribe")

			elif(subtype == "unsubscribe"):
				# User wants to unsubscribe to this contact's presence. (User is removing the contact from their list)
				debug.log("AIMConnection: Unsubscribe request received.")
				def cb(arg=None):
					updatePresence("unsubscribed")

				savethisuser = None
				savethisgroup = None
				for g in self.bos.ssigroups:
					for u in g.users:
						debug.log("Comparing %s and %s" % (u.name.lower(), userHandle))
						if (u.name.lower() == userHandle):
							debug.log("Located user %s in %s" % (u.name, g.name))
							savethisuser = u
							savethisgroup = g

				if (savethisuser is None):
					debug.log("Did not find user")
					return

				self.bos.startModifySSI()
				self.bos.delItemSSI(savethisuser).addCallback(cb)
				savethisgroup.delUser(savethisuser)
				self.bos.modifyItemSSI(savethisgroup)
				self.bos.endModifySSI()

                        elif(subtype == "unsubscribed"):
                                # The user wants to remove this contact's authorisation. Contact will no longer be able to see user
				debug.log("AIMConnection: Unsubscribed request received.")
				pass

                else: # The user wants to change subscription to the transport
                        if(subtype == "subscribe"):
                                updatePresence("subscribed")

                        elif(subtype == "subscribed"):
                                return # Nothing to do

                        elif(subtype == "unsubscribe" or subtype == "unsubscribed"):
                                # They want to unregister. Ok, we can do that
                                jid = self.session.jabberID
				debug.log("Subscriptions: Session \"%s\" is about to be unregistered" % (jid))
				self.session.pytrans.registermanager.removeRegInfo(jid)
				debug.log("Subscriptions: Session \"%s\" has been unregistered" % (jid))


	# Callbacks
	def errorCallback(self, result):
		debug.log("AIMConnection: errorCallback %s" % (result.getErrorMessage()))
		errmsg = result.getErrorMessage()
		errmsgs = errmsg.split("'")
		message = "Authentication Error!" 
		if (errmsgs[1]):
			message = message+"\n"+errmsgs[1]
		if (errmsgs[3]):
			message = message+"\n"+errmsgs[3]
		tmpjid = config.jid
		if (self.session.registeredmunge):
			tmpjid = tmpjid + "/registered"
		self.session.sendMessage(to=self.session.jabberID, fro=tmpjid, body=message, mtype="chat")

		self.session.removeMe()



#############################################################################
# AIMContacts
#############################################################################
class AIMContacts:
	def __init__(self, session):
		self.session = session
		self.ssicontacts = { }
		self.xdbcontacts = self.getXDBBuddies()
		self.xdbchanged = False

	def updateSSIContact(self, contact, presence="unavailable", show=None, status=None, skipsave=False):
		debug.log("AIMContacts: updating contact %s" % (contact.lower()))
		self.ssicontacts[contact.lower()] = {
			'presence': presence,
			'show': show,
			'status': status
		}

		if (not self.xdbcontacts.count(contact.lower())):
			from glue import aim2jid
			self.session.sendRosterImport(aim2jid(contact), "subscribe", "both", contact)
			self.xdbcontacts.append(contact.lower())
			self.xdbchanged = True
			if (not skipsave):
				self.saveXDBBuddies()

	def getXDBBuddies(self):
		debug.log("AIMContacts: getXDBBuddies %s %s" % (config.jid, self.session.jabberID))
		bl = list()
		result = self.session.pytrans.xdb.request(self.session.jabberID, "aimtrans:roster")
		if (result == None):
			debug.log("AIMContacts: getXDBBuddies unable to get list, or empty")
			return bl

		for child in result.elements():
			try:
				if(child.name == "buddies"):
					for item in child.elements():
						try:
							if(item.name == "item"):
								bl.append(item.getAttribute("name"))
						except AttributeError:
							continue
			except AttributeError:
				continue
		return bl

	def saveXDBBuddies(self):
		debug.log("AIMContacts: setXDBBuddies %s %s" % (config.jid, self.session.jabberID))
		if (not self.xdbchanged):
			debug.log("AIMContacts: nothing has changed, no need to save")
			return
		newXDB = Element((None, "aimtrans"))
		newXDB.attributes["xmlns"] = "aimtrans:roster"
		buddies = newXDB.addElement("buddies")

		for c in self.xdbcontacts:
			try:
				item = buddies.addElement("item")
				item.attributes["name"] = c

			except:
				pass

		self.session.pytrans.xdb.set(self.session.jabberID, "aimtrans:roster", newXDB)
		self.xdbchanged = False
		debug.log("AIMContacts: contacts saved")
